package org.tangjl.utill;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.artofsolving.jodconverter.DocumentConverter;
import com.artofsolving.jodconverter.openoffice.connection.OpenOfficeConnection;
import com.artofsolving.jodconverter.openoffice.connection.OpenOfficeException;
import com.artofsolving.jodconverter.openoffice.connection.SocketOpenOfficeConnection;
import com.artofsolving.jodconverter.openoffice.converter.OpenOfficeDocumentConverter;

/**
 * 文档转换
 * 
 * @author 唐家林
 * @time 2014年10月21日
 */
public class DocumentConverterUtil {
	private static Logger logger = LoggerFactory.getLogger(DocumentConverterUtil.class);

	/**
	 * 连接OpenOfiice的默认端口
	 */
	private static final int DEFAULT_PORT = 8100;

	/**
	 * 启动OpenOffice进程
	 * soffice -headless -accept="socket,host=127.0.0.1,port=8100;urp;" -nofirststartwizard
	 */
	private static final String START_CMD = "soffice -headless -accept=\"socket,host=127.0.0.1,port=" + DEFAULT_PORT + ";urp;\" -nofirststartwizard";

	/**
	 * 结束OpenOffice进程
	 * TASKKILL /F /im soffice.exe*
	 */
	private static final String STOP_CMD = "TASKKILL /F /im soffice.exe*";

	/**
	 * 连接OpenOfiice的端口
	 */
	private int PORT;

	/**
	 * 文档转换的文档
	 */
	private File FILE;

	/**
	 * 生成的PDF文件路径
	 */
	private File PDF_FILE;

	/**
	 * 生成的SWF文件路径
	 */
	private File SWF_FILE;
	
	private DocumentConverterUtil() {
	}
	
	/**
	 * 转换文档
	 * 
	 * <pre>
	 * 先从文档转换为PDF格式文件
	 * 然后将生成的PDF格式文件转为SWF格式文件
	 * </pre>
	 * 
	 * @param port
	 *            OpenOfiice监听端口
	 * @param filePath
	 *            需要转换的文档全路径
	 * @param pdfFolderPath
	 *            生成PDF文件的路径
	 * @param swfFolderPath
	 *            生成SWF文件的路径
	 * @param constraint
	 *            true:强制转换(当源文件内容发生变化,或其他因素导致需要忽略已生成的文件继续强制转换)
	 *            false:当已存在转换后的文件则不转换
	 * @param deletePdf
	 *            true:删除生成的PDF文件 false:保留生成的PDF文件
	 * @return 返回转换后的SWF文件,转换失败返回null
	 * @throws Exception 
	 */
	public static File conver (
			int port,
			String filePath,
			String pdfFolderPath,
			String swfFolderPath,
			boolean constraint,
			boolean deletePdf ) throws Exception {
		DocumentConverterUtil dc = new DocumentConverterUtil();
		
		if (isEmptyPath(filePath) || !isLegal(filePath)) {
			throw new RuntimeException("路径不能为空");
		}
		// 一些初始化工作
		dc.init(port, filePath, pdfFolderPath, swfFolderPath);

		if (!dc.SWF_FILE.exists() || constraint) {
			if (!isPDF(filePath)) {
				// 将文档转为PDF格式文件
				dc.converterToPDF(constraint);
			}
			// 将生成的PDF格式文件转为SWF格式文件
			dc.converterPDFToSWF(constraint, deletePdf);
		} else {
			log("该文档对应SWF格式文件已存在跳过转换步骤");
		}

		// 转换后再次判断文件是否存在
		if (!dc.SWF_FILE.exists()) {
			log("文件转换失败!");
			return null;
		}
		return dc.SWF_FILE;
	}

	/**
	 * 转换文档
	 * 
	 * <pre>
	 * 使用端口8100连接OpenOfiice
	 * 转换后的PDF和SWF文件存放在源文件所在文件夹
	 * 如果已经有对应的SWF文件则不转换
	 * 转换后不删除转换的PDF格式文件
	 * 先从文档转换为PDF格式文件
	 * 然后将生成的PDF格式文件转为SWF格式文件
	 * </pre>
	 * 
	 * @param filePath
	 *            需要转换的文档全路径
	 * @return 返回转换后的SWF文件,转换失败返回null
	 * @throws Exception 
	 */
	public static File conver (String filePath) throws Exception {
		return conver(filePath, null, null, false, false);
	}

	/**
	 * 转换文档
	 * 
	 * <pre>
	 * 使用端口8100连接OpenOfiice
	 * 先从文档转换为PDF格式文件
	 * 然后将生成的PDF格式文件转为SWF格式文件
	 * </pre>
	 * 
	 * @param filePath
	 *            需要转换的文档全路径
	 * @param constraint
	 *            true:强制转换(当源文件内容发生变化,或其他因素导致需要忽略已生成的文件继续强制转换)
	 *            false:当已存在转换后的文件则不转换
	 * @param deletePdf
	 *            true:删除生成的PDF文件 false:保留生成的PDF文件
	 * @return 返回转换后的SWF文件
	 * @throws Exception
	 *             转换失败
	 */
	public static File conver(
			String filePath,
			boolean constraint,
			boolean deletePdf
			) throws Exception {
		return conver(filePath, null, null, constraint, deletePdf);
	}

	/**
	 * 使用端口8100连接OpenOfiice,转换文档
	 * 
	 * <pre>
	 * 先从文档转换为PDF格式文件
	 * 然后将生成的PDF格式文件转为SWF格式文件
	 * </pre>
	 * 
	 * @param filePath
	 *            需要转换的文档全路径
	 * @param pdfFolderPath
	 *            生成PDF文件的路径
	 * @param swfFolderPath
	 *            生成SWF文件的路径
	 * @param constraint
	 *            true:强制转换(当源文件内容发生变化,或其他因素导致需要忽略已生成的文件继续强制转换)
	 *            false:当已存在转换后的文件则不转换
	 * @param deletePdf
	 *            true:删除生成的PDF文件 false:保留生成的PDF文件
	 * @return 返回转换后的SWF文件,转换失败返回null
	 * @throws Exception 
	 */
	public static File conver (
			String filePath,
			String pdfFolderPath,
			String swfFolderPath,
			boolean constraint,
			boolean deletePdf ) throws Exception {
		return conver(DEFAULT_PORT, filePath, pdfFolderPath, swfFolderPath, constraint, deletePdf);
	}

	/**
	 * 判断路是否为空
	 * 
	 * @param path
	 *            路径
	 * @return 为空返回true,不为空返回false
	 */
	public static boolean isEmptyPath(String path) {
		return null == path || path.isEmpty();
	}

	/**
	 * 判断所转换文件类型是否合法
	 * 
	 * @param fileName
	 *            文件名称(可以是文件名(必须带格式),可以是文件完整路径)
	 * @return 当且仅当合法返回true
	 */
	public static boolean isLegal(String fileName) {
		if (isEmptyPath(fileName)) {return false;}
		String regex = ".*\\.(txt|pdf|(doc|ppt|xls)x?)";
		return fileName.toLowerCase().matches(regex);
	}
	
	/**
	 * 是否为PDF文件
	 * 
	 * @param fileName
	 *            文件名称(可以是文件名(必须带格式),可以是文件完整路径)
	 * @return 是PDF文件返回true,否则返回false
	 */
	public static boolean isPDF(String fileName) {
		if (isEmptyPath(fileName)) {return false;}
		String regex = ".*\\.pdf";
		return fileName.toLowerCase().matches(regex);
	}
	
	/**
	 * 读取输出的文件,以此等待文件输出完成,否则文件在还未生成则返回了导致打开文件失败
	 * @param in 输入流
	 * @return 返回读取的信息
	 * @throws IOException
	 */
	private static void loadStream(InputStream in) throws IOException {
		in = new BufferedInputStream(in);
		while (in.read() != -1);
		in.close();
	}

	/**
	 * 将PDF文件转换为SWF文件
	 * 
	 * @param deletePdf
	 *            是否删除PDF文件.true:删除;false:保留
	 * @throws IOException
	 */
	private void converterPDFToSWF(boolean deletePdf) throws IOException {
		// 判断父文件夹是否存在,如果不存在则递归生成所需文件夹
		if (!SWF_FILE.getParentFile().exists()) {
			SWF_FILE.getParentFile().mkdirs();
		}
		
		String command = "pdf2swf \"%s\" -o \"%s\" -T 9";
		command = String.format(command, PDF_FILE.getPath(), SWF_FILE.getPath());
		log("PDF转SWF命令:" + command);
		
		log("SWF转换器开始工作，当前设置运行环境:" + System.getProperty("os.name"));
		Runtime r = Runtime.getRuntime();
		Process p = r.exec(command);
		// 等待转换完成
		loadStream(p.getInputStream());

		log("SWF转换成功，文件输出：" + SWF_FILE.getPath());

		// 如果要删除PDF文件,执行删除操作
		if (deletePdf && PDF_FILE.exists()) {
			PDF_FILE.delete();
		}
	}

	/**
	 * 将PDF文件转换为SWF文件
	 * 
	 * @param constraint
	 *            是否强制转换.true:文件已经存在仍转换;false:文件已经存在则不转换
	 * @param deletePdf
	 *            是否删除PDF文件 true:删除;false:保留
	 * @return 返回转换后的文件,转换失败返回null
	 * @throws Exception
	 */
	protected File converterPDFToSWF(boolean constraint, boolean deletePdf) throws IOException {
		if (!SWF_FILE.exists() || constraint) {
			if (PDF_FILE.exists()) {
				converterPDFToSWF(deletePdf);
			} else {
				log("PDF不存在,无法转换");
				throw new RuntimeException("PDF不存在,无法转换");
			}
		} else {
			log("SWF已经存在不需要转换");
		}
		log(SWF_FILE.getPath());
		return SWF_FILE;
	}

	/**
	 * 将文档转换为PDF文件
	 * 
	 * @param constraint
	 *            true:强制转换(当源文件内容发生变化,或其他因素导致需要忽略已生成的文件继续强制转换)
	 *            false:当已存在转换后的文件则不转换
	 * @return 返回转换后的文件路径,转换失败返回null
	 * @throws Exception
	 */
	protected File converterToPDF(boolean constraint) throws Exception {
		if (!FILE.exists()) {
			log("PDF转换异常，需要转换的文档不存在，无法转换");
			throw new RuntimeException("PDF转换异常，需要转换的文档不存在，无法转换");
		}
		if (!PDF_FILE.exists() || constraint) {
			// 判断父文件夹是否存在,如果不存在则递归生成所需文件夹
			if (!PDF_FILE.getParentFile().exists()) {
				PDF_FILE.getParentFile().mkdirs();
			}
			ExecutorService executor = Executors.newSingleThreadExecutor();
			final OpenOfficeConnection connection = new SocketOpenOfficeConnection(PORT);
			FutureTask<Boolean> future = new FutureTask<Boolean>(new Callable<Boolean>(){
				public Boolean call() throws Exception {
					try {
						connection.connect();
						DocumentConverter converter = new OpenOfficeDocumentConverter(connection);
						converter.convert(FILE, PDF_FILE);
						return true;
					} catch (java.net.ConnectException e) {
						String msg1 = "PDF转换器异常，OpenOffice服务未启动！";
						String msg2 = "OpenOffice服务启动:" + START_CMD;
						log(msg1);
						log(msg2);
						throw new RuntimeException(msg1 + "\r\n" + msg2, e);
					} catch (OpenOfficeException e) {
						log("PDF转换器异常，读取转换文件失败");
						throw new RuntimeException("PDFF转换器异常，读取转换文件失败", e);
					}
				}
			});
			try {
				executor.execute(future);
				boolean isSuccess = future.get(60, TimeUnit.SECONDS);
				if (!isSuccess) { throw new RuntimeException("文件转换失败,转换超时"); }
			} catch (RuntimeException e) {
				throw e;
			} catch (InterruptedException | ExecutionException e) {
				throw new RuntimeException("文件转换失败", e);
			} catch (TimeoutException e) {
				try {
					// 杀掉进程并等待执行完毕
					Runtime.getRuntime().exec(STOP_CMD).waitFor();
					// 启动进程
					Runtime.getRuntime().exec(START_CMD);
				} catch (IOException | InterruptedException e1) {}
				throw new RuntimeException("文件转换失败,转换超时", e);
			} finally {
				if (null != connection) { connection.disconnect(); }
				if (null != executor && !executor.isShutdown()) { executor.shutdown(); }
			}
			log("PDF转换成功，PDF输出：" + PDF_FILE.getPath());
		} else {
			log("已经转换为PDF，不需要再进行转换");
		}
		log(PDF_FILE.getPath());
		return PDF_FILE;
	}

	/**
	 * 
	 * @param port OpenOfiice启动端口
	 * @param filePath 文件路径
	 * @param pdfFolderPath
	 *            转换后的PDF文件存放路径
	 * @param swfFolderPath
	 *            转换后的SWF文件存放路径
	 */
	private void init(
			int port,
			String filePath,
			String pdfFolderPath,
			String swfFolderPath ) {
		PORT = port;
		FILE = new File(filePath);
		// 去掉后缀名,取得输出文件的路径和名称
		String outPath = filePath.substring(0, filePath.lastIndexOf("."));
		String fileName = FILE.getName();
		fileName = File.separator + fileName.substring(0, fileName.lastIndexOf("."));
		
		if (isPDF(filePath)) {
			PDF_FILE = new File(filePath);
		} else {
			PDF_FILE = new File((isEmptyPath(pdfFolderPath) ? outPath : (pdfFolderPath + fileName)) + ".pdf");
		}
		SWF_FILE = new File((isEmptyPath(swfFolderPath) ? outPath : (swfFolderPath + fileName)) + ".swf");
	}

	/**
	 * 调试日志输出
	 * 
	 * @param msg
	 */
	private static void log(String msg) {
		if (logger.isDebugEnabled()) {
			logger.debug(msg);
		}
	}
}